// UNIXListenerSocket.cpp
//
// Copyright (c) 2008 Bojan Rajkovic
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
//

#include "../include/SocketWrapper.h"

UNIXListenerSocket::UNIXListenerSocket() {
	this->Open(strdup(SOCK_PATH));
}

UNIXListenerSocket::UNIXListenerSocket(std::string SocketLocation) {
	this->Open(SocketLocation);
}

UNIXListenerSocket::~UNIXListenerSocket() {
	fclose(this->remoteSocketFileDescriptor);
	close(this->remoteSocketDescriptor);
	fclose(this->socketFileDescriptor);
	close(this->socketDescriptor);
}

void UNIXListenerSocket::Open(std::string SocketLocation) {
	
	this->socketDescriptor = socket(AF_UNIX, SOCK_STREAM, 0);
	
	if (this->socketDescriptor == -1) throw SocketException(errno);
	
	this->socketFileDescriptor = fdopen(this->socketDescriptor, "r+");
	
	// Set up some temporary variables
	struct sockaddr_un localAddress;
	int len;
	
	// Set up the sockaddr_un struct's members
	localAddress.sun_family = AF_UNIX;
	strcpy(localAddress.sun_path, SocketLocation.c_str());
	// Remove the existing socket, if there is one
	unlink(localAddress.sun_path);
	
	// get the general size of sockaddr_un
	len = strlen(localAddress.sun_path) + sizeof(localAddress.sun_family);
	
	// bind the socket
	if (bind(this->socketDescriptor, reinterpret_cast<struct sockaddr *>(&localAddress), len) == -1) throw SocketException(errno);

	if (listen(this->socketDescriptor, 0) == -1) throw SocketException(errno);
	
	// We'll set this to -1, and then check if it's still -1 in calls that need Accept() to have been called first.
	this->remoteSocketDescriptor = -1;
}

void UNIXListenerSocket::Accept() {
	
	struct sockaddr_un remoteAddress;
	size_t remoteSize = sizeof(remoteAddress);
		
	this->remoteSocketDescriptor = accept(this->socketDescriptor, 
		reinterpret_cast<struct sockaddr *>(&remoteAddress), reinterpret_cast<socklen_t*>(&remoteSize));
		
	if (this->remoteSocketDescriptor == -1) throw SocketException(errno);
	
	this->remoteSocketFileDescriptor = fdopen(this->remoteSocketDescriptor, "r+");
}

int UNIXListenerSocket::Write(std::string Data) {
	
	if (this->remoteSocketDescriptor == -1) throw SocketException("Please call Accept before calls to Read.");
	
	// We'll just flat out send the data, there's not much to much aroud with here
	// Return the return value of send because it indicates how many bytes were sent
	// No need for flags, nothing applies.
	int retval = send(this->remoteSocketDescriptor, Data.c_str(), Data.size(), 0);
	
	if (retval < 0) throw SocketException(errno);
	else return retval;
}

byte* UNIXListenerSocket::ReadBytes(int Bytes) {
	
	if (this->remoteSocketDescriptor == -1) throw SocketException("Please call Accept before calls to Read.");
	
	// Set up a buffer
	byte* buffer = static_cast<byte*>(malloc(sizeof(byte)*Bytes));
	
	// We'll use recv again, with MSG_WAITALL because we want to fill our quota
	// If we got -1, there's an error, throw an exception.
	if (recv(this->remoteSocketDescriptor, buffer, Bytes, MSG_WAITALL) < 0) {
		throw SocketException(errno);
	}
	
	// No need to massage the data, just send it back.
	// Just a note, this buffer does need to be free()'ed after use.
	return buffer;
}

std::string UNIXListenerSocket::Read(int ReadSize) {
	
	if (this->remoteSocketDescriptor == -1) throw SocketException("Please call Accept before calls to Read.");
	
	// First, set up a buffer to read into. That extra byte is for the null terminator.
	char* buffer = static_cast<char*>(malloc(sizeof(char)*ReadSize+1));
	
	// We'll use recv with the MSG_WAITALL flag because we want to be sure we get ReadSize bytes.
	// If we got -1, there's an error, throw an exception.
	if (recv(this->remoteSocketDescriptor, buffer, ReadSize+1, MSG_WAITALL) < 0) {
		throw SocketException(errno);
	}
	
	// Well, if we got this far, we got all the data we wanted.
	// Let's make it into a std::string and give it back.
	std::string returnString(buffer);
	return returnString;
}

std::string UNIXListenerSocket::ReadLine() {
	
	if (this->remoteSocketDescriptor == -1) throw SocketException("Please call Accept before calls to Read.");
	
	// Again, we need to set up a buffer to read into, plus the extra byte
	// We'll allocate 4 KiB for the line, and hope it's not that long
	
	char* buffer = static_cast<char*>(malloc(sizeof(char)*LINE_BUFFER_SIZE));	
	
	// We'll use fgets this time, since we're largely mimicing its behavior
	buffer = fgets(buffer, sizeof(char)*LINE_BUFFER_SIZE, this->remoteSocketFileDescriptor);
	
	// If fgets gave us back NULL, we know that the underlying socket must've died.
	// There's no other reason that the FILE* should be dead.
	if (buffer == NULL) return "";
	
	// Build the string and ship it back
	std::string line(buffer);
	return line;	
}
